﻿using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Commnications
{
    public static class EasyHelper
    {
        public static EasyContext CreateTcpV2()
        {
            return new EasyContext(CreateTcp(), true);
        }
        public static EasyContext CreateUdpV2(string host, int port, bool isBindPort = false)
        {
            return new EasyContext(CreateUdp(host, port, isBindPort), false) { RemoteEndPoint = EasyHelper.GetIPEndPoint(host, port, false) };
        }
        public static EasyContext CreateUdpV2(string host, int port, string remoteHost, int remotePort)
        {
            return new EasyContext(CreateUdp(host, port, true), remoteHost, remotePort);
        }
        public static EasyContext CreateCom(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits, Action<EasyContext, ChannelType> action = null)
        {
            var serial = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
            return new EasyContext(serial, action);
        }
        public static EasyContext CreateTcpServer(int port, string host = null)
        {
            // 创建TCP Socket（IPv4，流式传输，TCP协议）
            var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress ipAddress = IPAddress.Any;
            if (!string.IsNullOrWhiteSpace(host) && IPAddress.TryParse(host, out var address))
                ipAddress = address;
            // 绑定IP和端口
            serverSocket.Bind(new IPEndPoint(ipAddress, port));
            return new EasyContext(serverSocket, ChannelType.Tcp);
        }

        /// <summary>
        /// udp
        /// </summary>
        /// <param name="host">本机ip地址</param>
        /// <param name="port">绑定端口号</param>
        /// <returns></returns>
        public static Socket CreateUdp(string host, int port, bool isBindPort = false)
        {
            var serverPoint = GetIPEndPoint(host, port);
            if (serverPoint != null)
            {
                var serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                serverSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                //IPEndPoint serverPoint = new IPEndPoint(hostAddr, port);
                if (isBindPort)
                    serverSocket.Bind(serverPoint);
                return serverSocket;
            }
            else
                return null;
        }
        //public static Socket CreateUdp(string host, int port, string remoteHost, int remotePort)
        //{
        //    var socket = CreateUdp(host, port);
        //    return socket != null && GetIPEndPoint(remoteHost, remotePort);
        //}
        public static IPEndPoint GetIPEndPoint(string host, int port, bool isLoopCheck = true)
        {
            if (IPAddress.TryParse(host, out var address))
            {
                IPAddress hostAddr = address;
                if (isLoopCheck && IPAddress.IsLoopback(address))
                    hostAddr = IPAddress.Any;
                IPEndPoint serverPoint = new IPEndPoint(hostAddr, port);
                return serverPoint;
            }
            return null;
        }
        public static Socket CreateTcp()
        {
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            return socket;
        }
        public static bool ConnectAsync(string ip, int port, int timeout, Socket socket)
        {
            var timeState = new TimeState() { Handle = socket };
            socket.BeginConnect(ip, port, iar =>
            {
                var client = (TimeState)iar.AsyncState;
                try
                {
                    client.Handle.EndConnect(iar);
                }
                catch (Exception)
                {
                    client.IsTimeout = true;
                    //logger?.Error("brother ConnectAsync error:{0}", e.ToString());
                }
                finally
                {
                    try
                    {
                        if (!timeState.IsDisposed)
                            client.ConDone.Set();
                    }
                    catch (Exception)
                    {
                    }
                }
            }, timeState);
            bool isWaitSuccess = true;
            if (!timeState.ConDone.WaitOne(timeout))
            {
                isWaitSuccess = false;
            }
            timeState.SetDispose();

            return isWaitSuccess && !timeState.IsTimeout && socket.Connected;
        }

        private class TimeState //: IDisposable
        {
            public Socket Handle;
            public AutoResetEvent ConDone = new AutoResetEvent(false);
            public bool IsTimeout = false;
            public bool IsDisposed { get; private set; }

            public void SetDispose()
            {
                IsDisposed = true;
                try
                {
                    ConDone.Dispose();
                }
                catch (Exception)
                {
                }
            }
        }
    }
}
